在以后互斥锁就简称是锁,除非遇到递归锁才叫回互斥锁
锁: 保证多个线程修改进程中同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,使用锁后速度会变慢,但牺牲了速度却保证了数据安全。
锁其实就是将异步执行变成了同步执行
虽然有了GIL锁,但是在多线程中的特殊情况数据也还是不安全的,仍然要加锁
- GIL锁 是锁线程的,普通的锁是锁数据的
- 正常情况下不加锁,而是使用cpython解析器 GIL 锁,数据还是安全的,但是在使用多线程去修改进程中同一个数据的时候还是加上锁比较好
线程中的锁和进程中的锁使用方式还是一样的
1. 正常情况下不加锁,数据还是安全的,因为有GIL锁
from threading import Thread
def fun():
global n
n -= 1 # 多个线程修改进程中的同一个数据
n = 100
for i in range(100):
t = Thread(target=fun)
t.start()
print(n) # 0
2. 特殊情况就要上锁
- 特殊情况: 多个线程去修改进程中的同一个数据的使用,当其中一个线程拿到了数据且将该数据放到寄存器里准备开始进行运算的时候,此时时间片就开始轮转,下个线程就开始从进程中获取数据(此时的数据还是原来的数据而不是上一个线程修改后的数据,因为还没有来得及修改时间片就开始轮转)进行修改,当这个线程修改完后时间片又开始轮转回之前那个还没来得及修改的线程中继续进行修改,且此时修改的结果和他下个修改的结果是一样的,因为他们从进程中获取到的数据还是原来的数据,这样就会造成数据的混乱和进程中的数据混乱是一样的
# 没有上锁的特殊情况
import time
from threading import Thread
def fun():
global n
# 特殊情况:不是用 -= ,而是拆开运算
temp = n
time.sleep(0.01)
n = temp - 1
n = 100
for i in range(100):
t = Thread(target=fun)
t.start()
print(n) # 99
# 上锁解决特殊情况
import time
from threading import Thread
from threading import Lock
def fun():
global n
# 特殊情况:不是用 -= ,而是拆开运算
lock.acquire() # 上锁 -> 直接调用外部的变量, 因为一个进程中的多个线程是可以直接使用这个进程中的数据,且一个py文件就相当于一个进程,不按照线程的说法: 函数本来就可以调用外部的变量和方法,因为作用域链
temp = n
time.sleep(0.01)
n = temp - 1
lock.release() # 解锁 -> 直接调用外部的变量, 因为一个进程中的多个线程是可以直接使用这个进程中的数据,且一个py文件就相当于一个进程,不按照线程的说法: 函数本来就可以调用外部的变量和方法,因为作用域链
n = 100
lock = Lock()
t_list = []
for i in range(100):
t = Thread(target=fun)
t.start()
t_list.append(t)
[i.join() for i in t_list] # 等待所有子线程执行完后再往下执行
print(n) # 99